package coderx

import (
	"encoding/binary"
	"fmt"
	"gitee.com/zhongguo168a/gocodes/datax"
	"gitee.com/zhongguo168a/gocodes/datax/binaryx"
	"gitee.com/zhongguo168a/gocodes/datax/convertx"
	"gitee.com/zhongguo168a/gocodes/datax/listx"
	"gitee.com/zhongguo168a/gocodes/datax/mapx"
	"gitee.com/zhongguo168a/gocodes/datax/schemax"
	"gitee.com/zhongguo168a/gocodes/datax/schemax/basickind"
	"gitee.com/zhongguo168a/gocodes/myx/errorx"
	"io"
	"sort"
	"strconv"
)

func NewMapToByteWithType(_type string) (obj *MapToByte, err error) {
	schema := schemax.GetDeclByKey(_type)
	if schema == nil {
		err = errorx.New("schema not found: ", datax.M{"_type": _type})
		return
	}
	obj = NewMapToByteWithSchema(schema.(*schemax.ClassDecl))
	return
}

func NewMapToByteWithSchema(schema *schemax.ClassDecl) (obj *MapToByte) {
	obj = NewMapToByte()
	obj.schema = schema

	return
}

func NewMapToByte() (obj *MapToByte) {
	obj = &MapToByte{}
	obj.endian = binary.LittleEndian
	return
}

type MapToByte struct {
	schema *schemax.ClassDecl

	ignoreAny bool
	//
	change bool
	//
	endian binary.ByteOrder
}

func (coder *MapToByte) Reset() {
	coder.schema = nil
	coder.ignoreAny = false
	coder.endian = binary.LittleEndian
	coder.change = false
}

func (coder *MapToByte) SetByteOrder(order binary.ByteOrder) *MapToByte {
	coder.endian = order
	return coder
}

// WithIgnoreAny 设置 IgnoreAny 后， 遇到 Any 类型的字段将被忽略，否则会返回错误
func (coder *MapToByte) WithIgnoreAny() *MapToByte {
	coder.ignoreAny = true
	return coder
}

func (coder *MapToByte) WithChange() *MapToByte {
	coder.change = true
	return coder
}

func (coder *MapToByte) Write(source map[string]interface{}, writer io.Writer) (err error) {
	err = binaryx.WriteBool(writer, binary.BigEndian, coder.change)
	err = coder.exportAllObj(source, writer, coder.schema)
	if err != nil {
		err = errorx.Wrap(err, coder.schema.Name)
		return
	}
	return
}

// keyMode 0-source的字段, 1-所有字段
func (coder *MapToByte) exportAllObj(sourceMap map[string]interface{}, writer io.Writer, schema schemax.IDecl) (err error) {
	var (
		decl = schema.(*schemax.ClassDecl)
	)
	allfields := decl.GetAllField()

	if coder.change {
		l := &listx.List{}
		for _, field := range allfields {
			l.Push(field)
		}
		var newfields []*schemax.Field
		group := l.GroupByCount(8)
		for _, val := range group {
			state := uint(0)
			val.ForRange(func(index int, item interface{}) error {
				field := item.(*schemax.Field)
				fname := field.Name
				_, mhas := sourceMap[fname]
				if mhas {
					state = binaryx.SetState(state, 1<<index, true)
					newfields = append(newfields, field)
				}

				return nil
			})
			err = binaryx.WriteUint8(writer, binary.BigEndian, uint8(state))
		}

		allfields = newfields
	}

	for _, field := range allfields {
		fname := field.Name
		mval, mhas := sourceMap[fname]
		switch ftyp := field.Type.(type) {
		case *schemax.ClassType:
			err = coder.exportAllClass(mhas, mval, writer, ftyp.Decl)
		case *schemax.ArrayType:
			err = coder.exportAllSlice(mhas, mval, writer, fname, ftyp)
		case *schemax.MapType:
			err = coder.exportAllMap(mhas, fname, mval, writer, ftyp)
		case *schemax.EnumType:
			err = coder.exportAllEnum(mval, writer, ftyp.Decl)
		case *schemax.BasicType:
			err = coder.exportAllBasic(mval, writer, ftyp.Kind)
		case *schemax.AnyType:
			if !coder.ignoreAny {
				err = errorx.New("not support type any")
				return
			}
		default:
		}
		if err != nil {
			err = errorx.Wrap(err, fname)
			return
		}
	}
	return
}

func (coder *MapToByte) exportAllClass(has bool, many interface{}, writer io.Writer, decl string) (err error) {
	var (
		isNil = false
		mmap  map[string]interface{}
		isMap bool
	)
	if !has {
		isNil = true
	} else {
		if many == nil {
			isNil = true
		} else {
			mmap, isMap = many.(map[string]interface{})
			if !isMap {
				err = errorx.Wrap(err, fmt.Sprintf("map value is not map[string]interface{}"))
				return
			}
			isNil = mmap == nil
		}
	}

	err = binaryx.WriteBool(writer, binary.BigEndian, isNil)
	if err != nil {
		err = errorx.Wrap(err, fmt.Sprintf("write map nil"))
		return
	}
	if isNil {
		return
	}
	stdesc := schemax.GetDeclByKey(decl)
	if stdesc == nil {
		err = errorx.New("schema not found: ", datax.M{"decl": decl})
		return
	}

	err = coder.exportAllObj(many.(map[string]interface{}), writer, stdesc)
	return
}
func (coder *MapToByte) exportAllEnum(mval interface{}, writer io.Writer, decl string) (err error) {
	idesc := schemax.GetDeclByKey(decl)
	if idesc == nil {
		err = errorx.New("schema not found", datax.M{"decl": decl})
		return
	}

	enumdesc := idesc.(*schemax.EnumDecl)
	err = coder.exportAllBasic(enumdesc.ConvertToValue(mval), writer, basickind.Kind(enumdesc.Kind))
	if err != nil {
		return
	}
	return
}

func (coder *MapToByte) exportAllSlice(has bool, many interface{}, writer io.Writer, fname string, ftyp *schemax.ArrayType) (err error) {

	if !has || many == nil {
		err = binaryx.WriteInt16(writer, binary.BigEndian, int16(0))
		if err != nil {
			err = errorx.Wrap(err, fmt.Sprintf("write array length zero"))
			return
		}
		return
	}
	marr, ok := many.([]interface{})
	if !ok {
		err = errorx.New("value is not []interface{}")
		return
	}

	var mlen = len(marr)
	err = binaryx.WriteInt16(writer, binary.BigEndian, int16(mlen))
	if err != nil {
		err = errorx.Wrap(err, fmt.Sprintf("write array length"))
		return
	}
	if mlen == 0 {
		return
	}
	switch etyp := ftyp.Elem.(type) {
	case *schemax.ClassType:
		for i := 0; i < len(marr); i++ {
			mval := marr[i]
			err = coder.exportAllClass(has, mval, writer, etyp.Decl)
			if err != nil {
				err = errorx.Wrap(err, strconv.Itoa(i))
				return
			}
		}
	case *schemax.BasicType:
		for i := 0; i < len(marr); i++ {
			err = coder.exportAllBasic(marr[i], writer, etyp.Kind)
			if err != nil {
				err = errorx.Wrap(err, strconv.Itoa(i))
				return
			}
		}
	case *schemax.EnumType:
		for i := 0; i < len(marr); i++ {
			mval := marr[i]
			err = coder.exportAllEnum(mval, writer, etyp.Decl)
			if err != nil {
				err = errorx.Wrap(err, strconv.Itoa(i))
				return
			}
		}
	default:
		err = errorx.New("decode array: not support type: " + etyp.String())
		return

	}

	return
}

func (coder *MapToByte) exportAllMap(has bool, fname string, many interface{}, writer io.Writer, ftyp *schemax.MapType) (err error) {
	isNil := func() (x bool) {
		return !has || many == nil
	}()
	err = binaryx.WriteBool(writer, binary.BigEndian, isNil)

	if isNil {
		return
	}
	mmap, ok := many.(map[string]interface{})
	if !ok {
		err = errorx.New("value is not map[string]interface{}")
		return
	}
	//
	keys := mapx.Keys(mmap)
	sort.Strings(keys)

	err = binaryx.WriteInt16(writer, binary.BigEndian, int16(len(keys)))
	if err != nil {
		err = errorx.Wrap(err, fmt.Sprintf("write map length"))
		return
	}

	for _, mkey := range keys {
		mval := mmap[mkey]
		err = binaryx.WriteUTF(writer, binary.BigEndian, mkey)
		if err != nil {
			err = errorx.Wrap(err, fmt.Sprintf("write map key %v", mkey))
			return
		}
		switch etyp := ftyp.Value.(type) {
		case *schemax.ClassType:
			mval, has := mmap[mkey]
			err = coder.exportAllClass(has, mval, writer, etyp.Decl)
			if err != nil {
				err = errorx.Wrap(err, mkey)
				return
			}
		case *schemax.BasicType:
			err = coder.exportAllBasic(mval, writer, etyp.Kind)
			if err != nil {
				err = errorx.Wrap(err, mkey)
				return
			}
		case *schemax.EnumType:
			err = coder.exportAllEnum(mval, writer, etyp.Decl)
			if err != nil {
				err = errorx.Wrap(err, mkey)
				return
			}
		default:
			err = errorx.New("not support type: " + etyp.String())
			return
		}
	}

	return
}

func (coder *MapToByte) exportAllBasic(mval interface{}, writer io.Writer, kind basickind.Kind) (err error) {
	switch kind {
	case basickind.Bool:
		err = binaryx.WriteBool(writer, binary.BigEndian, convertx.AnyToBool(mval))
	case basickind.Int8:
		err = binaryx.WriteInt8(writer, binary.BigEndian, int8(convertx.AnyToInt(mval)))
	case basickind.Int16:
		err = binaryx.WriteInt16(writer, binary.BigEndian, int16(convertx.AnyToInt(mval)))
	case basickind.Int32:
		err = binaryx.WriteInt32(writer, binary.BigEndian, int32(convertx.AnyToInt(mval)))
	case basickind.Int64:
		err = binaryx.WriteInt64(writer, binary.BigEndian, int64(convertx.AnyToInt(mval)))
	case basickind.Uint8:
		err = binaryx.WriteUint8(writer, binary.BigEndian, uint8(convertx.AnyToUint(mval)))
	case basickind.Uint16:
		err = binaryx.WriteUint16(writer, binary.BigEndian, uint16(convertx.AnyToUint(mval)))
	case basickind.Uint32:
		err = binaryx.WriteUint32(writer, binary.BigEndian, uint32(convertx.AnyToUint(mval)))
	case basickind.Uint64:
		err = binaryx.WriteUint64(writer, binary.BigEndian, uint64(convertx.AnyToUint(mval)))
	case basickind.Float32:
		err = binaryx.WriteFloat32(writer, binary.BigEndian, float32(convertx.AnyToFloat64(mval)))
	case basickind.Float64:
		err = binaryx.WriteFloat64(writer, binary.BigEndian, convertx.AnyToFloat64(mval))
	case basickind.String:
		err = binaryx.WriteUTF(writer, binary.BigEndian, convertx.AnyToString(mval))
	}
	if err != nil {
		err = errorx.Wrap(err, "write binary", datax.M{"kind": kind})
		return
	}
	return
}
